// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2019
// Distributed under the Boost Software License, Version 1.0.
// http://www.boost.org/LICENSE_1_0.txt
// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// File Version: 3.0.0 (2016/06/19)

#pragma once

#include <LowLevel/GteLogger.h>
#include <Mathematics/GteMatrix3x3.h>
#include <Mathematics/GteMatrix4x4.h>
#include <Mathematics/GteRotation.h>

// Transforms when using the convention GTE_USE_MAT_VEC.
//
// The transform is Y = M*X+T, where M is a 3-by-3 matrix and T is a 3x1
// translation.  In most cases, M = R, a rotation matrix, or M = R*S,
// where R is a rotation matrix and S is a diagonal matrix whose diagonal
// entries are positive scales.  To support modeling packages that allow
// general affine transforms, M can be any invertible 3x3 matrix.  The vector
// X is transformed in the "forward" direction to Y.  The "inverse" direction
// transforms Y to X, namely X = M^{-1}*(Y-T) in the general case.  In the
// special case of M = R*S, the inverse direction is X = S^{-1}*R^t*(Y-T),
// where S^{-1} is the diagonal matrix whose diagonal entries are the
// reciprocoals of those of S and where R^t is the transpose of R.  For SIMD
// support of matrix-vector and matrix-matrix multiplications, a homogeneous
// matrix H = {{M,T},{0,1}} is stored by this class.  The forward transform is
// {Y,1} = H*{X,1} and the inverse transform is {X,1} = H^{-1}*{Y,1}.

// Transforms when using the convention GTE_USE_VEC_MAT.
//
// The transform is Y = T + X*M, where M is a 3-by-3 matrix and T is a 1x3
// translation.  In most cases, M = R, a rotation matrix, or M = S*R,
// where R is a rotation matrix and S is a diagonal matrix whose diagonal
// entries are positive scales.  To support modeling packages that allow
// general affine transforms, M can be any invertible 3x3 matrix.  The vector
// X is transformed in the "forward" direction to Y.  The "inverse" direction
// transforms Y to X, namely X = (Y-T)*M^{-1} in the general case.  In the
// special case of M = S*R, the inverse direction is X = (Y-T)*R^t*S^{-1},
// where S^{-1} is the diagonal matrix whose diagonal entries are the
// reciprocoals of those of S and where R^t is the transpose of R.  For SIMD
// support of matrix-vector and matrix-matrix multiplications, a homogeneous
// matrix H = {{M,0},{T,1}} is stored by this class.  The forward transform is
// {Y,1} = {X,1}*H and the inverse transform is {X,1} = {Y,1}*H^{-1}.

// With either multiplication convention, a matrix M = R*S (GTE_USE_MAT_VEC)
// or a matrix M = S*R (GTE_USE_VEC_MAT) is referred to as an "RS-matrix".
// The class does not provide a member function to compute the inverse of a
// transform:  'Transform GetInverse() const'.  If one were to add this,
// be aware that the inverse of an RS-matrix is not generally an RS-matrix;
// that is, the inverse of R*S is S^{-1}*R^t which cannot always be factored
// as S^{-1} * R^t = R' * S'.  You would need to SetMatrix using S^{-1}*R^t
// as the input.

namespace gte
{

class Transform
{
public:
    // The default constructor produces the identity transformation.  The
    // default copy constructor and assignment operator are generated by the
    // compiler.
    Transform();

    // Implicit conversion.
    inline operator Matrix4x4<float> const&() const;

    // Set the transformation to the identity matrix.
    void MakeIdentity();

    // Set the transformation to have scales of 1.
    void MakeUnitScale();

    // Hints about the structure of the transformation.
    inline bool IsIdentity() const;      // I
    inline bool IsRSMatrix() const;      // R*S or S*R (depends on convention)
    inline bool IsUniformScale() const;  // RS-matrix with S = c*I

    // Member access.
    // (1) The Set* functions set the is-identity hint to false.
    // (2) The SetRotate function sets the is-rsmatrix hint to true.  If this
    //     hint is false,  GetRotate triggers an assertion in debug mode.
    // (3) The SetMatrix function sets the is-rsmatrix and is-uniform-scale
    //     hints to false.
    // (4) The SetScale function sets the is-uniform-scale hint to false.
    //     The SetUniformScale function sets the is-uniform-scale hint to
    //     true.  If this hint is false, GetUniformScale triggers an assertion
    //     in debug mode.
    // (5) All Set* functions set the inverse-needs-update to true.  When
    //     GetHInverse is called, the inverse must be computed in this case
    //     and the inverse-needs-update is reset to false.
    void SetRotation(Matrix4x4<float> const& rotate);   // {{R,0},{0,1}}
    void SetMatrix(Matrix4x4<float> const& matrix);     // {{M,0},{0,1}}
    void SetTranslation(float x0, float x1, float x2);
    void SetTranslation(Vector3<float> const& translate);
    void SetTranslation(Vector4<float> const& translate);
    void SetScale(float s0, float s1, float s2);
    void SetScale(Vector3<float> const& scale);
    void SetScale(Vector4<float> const& scale);
    void SetUniformScale(float scale);
    inline Matrix4x4<float> const& GetRotation() const; // {{R,0},{0,1}}
    inline Matrix4x4<float> const& GetMatrix() const;   // {{M,0},{0,1}}
    inline Vector3<float> GetTranslation() const;       // (x,y,z)
    inline Vector4<float> GetTranslationW0() const;     // (x,y,z,0)
    inline Vector4<float> GetTranslationW1() const;     // (x,y,z,1)
    inline Vector3<float> GetScale() const;             // (s0,s1,s2)
    inline Vector4<float> GetScaleW1() const;           // (s0,s1,s2,1)
    inline float GetUniformScale() const;

    // Alternate representations to set/get the rotation.

    // Set/get from 3x3 matrices.
    void SetRotation(Matrix3x3<float> const& rotate);
    void GetRotation(Matrix3x3<float>& rotate) const;

    // The quaternion is unit length.
    void SetRotation(Quaternion<float> const& q);
    void GetRotation(Quaternion<float>& q) const;

    // The axis is unit length and the angle is in radians.
    void SetRotation(AxisAngle<4, float> const& axisAngle);
    void GetRotation(AxisAngle<4, float>& axisAngle) const;

    // The Euler angles are in radians.  The GetEulerAngles function
    // expects the eulerAngles.axis[] values to be set to the axis order
    // you want.
    void SetRotation(EulerAngles<float> const& eulerAngles);
    void GetRotation(EulerAngles<float>& eulerAngles) const;

    // For M = R*S or M = S*R, the largest value of S in absolute value is
    // returned. For general M, the max-row-sum norm is returned for the
    // GTE_USE_MAT_VEC convention and the max-col-sum norm is returned for
    // the GTE_USE_VEC_MAT convetion, which is a reasonable measure of maximum
    // scale of the transformation.
    float GetNorm() const;

    // Get the homogeneous matrix (composite of all channels).
    inline Matrix4x4<float> const& GetHMatrix() const;

    // Get the inverse homogeneous matrix, recomputing it when necessary.
    // GTE_USE_MAT_VEC
    //     H = {{M,T},{0,1}}, then H^{-1} = {{M^{-1},-M^{-1}*T},{0,1}}
    // GTE_USE_VEC_MAT
    //     H = {{M,0},{T,1}}, then H^{-1} = {{M^{-1},0},{-M^{-1}*T,1}}
    Matrix4x4<float> const& GetHInverse() const;

    // Invert the transform.  If possible, the channels are properly
    // assigned.  For example, if the input has mIsRSMatrix equal to
    // 'true', then the inverse also has mIsRSMatrix equal to 'true'
    // and the inverse's mMatrix is a rotation matrix and mScale is
    // set accordingly.
    Transform Inverse() const;

    // The identity transformation.
    static Transform const IDENTITY;

private:
    // Fill in the entries of mHMatrix whenever one of the components
    // mMatrix, mTranslate, or mScale changes.
    void UpdateHMatrix();

    // Invert the 3x3 upper-left block of the input matrix.
    static void Invert3x3(Matrix4x4<float> const& mat,
        Matrix4x4<float>& invMat);

    // The full 4x4 homogeneous matrix H and its inverse H^{-1}, stored
    // according to the conventions (see GetHInverse description).  The
    // inverse is computed only on demand.
    Matrix4x4<float> mHMatrix;
    mutable Matrix4x4<float> mInvHMatrix;

    Matrix4x4<float> mMatrix;   // M (general) or R (rotation)
    Vector4<float> mTranslate;  // T
    Vector4<float> mScale;      // S
    bool mIsIdentity, mIsRSMatrix, mIsUniformScale;
    mutable bool mInverseNeedsUpdate;
};

// Compute M*V.
Vector4<float> operator*(Transform const& M, Vector4<float> const& V);

// Compute V^T*M.
Vector4<float> operator*(Vector4<float> const& V, Transform const& M);

// Compute A*B.
Transform operator*(Transform const& A, Transform const& B);

inline Matrix4x4<float> operator*(Matrix4x4<float> const& A,
    Transform const& B);

inline Matrix4x4<float> operator*(Transform const& A,
    Matrix4x4<float> const& B);


inline Transform::operator Matrix4x4<float> const&() const
{
    return GetHMatrix();
}

inline bool Transform::IsIdentity() const
{
    return mIsIdentity;
}

inline bool Transform::IsRSMatrix() const
{
    return mIsRSMatrix;
}

inline bool Transform::IsUniformScale() const
{
    return mIsRSMatrix && mIsUniformScale;
}

inline Matrix4x4<float> const& Transform::GetRotation() const
{
    LogAssert(mIsRSMatrix, "Transform is not rotation-scale.");
    return mMatrix;
}

inline Matrix4x4<float> const& Transform::GetMatrix() const
{
    return mMatrix;
}

inline Vector3<float> Transform::GetTranslation() const
{
    return Vector3<float>{mTranslate[0], mTranslate[1], mTranslate[2]};
}

inline Vector4<float> Transform::GetTranslationW0() const
{
    return Vector4<float>{mTranslate[0], mTranslate[1], mTranslate[2], 0.0f};
}

inline Vector4<float> Transform::GetTranslationW1() const
{
    return mTranslate;
}

inline Vector3<float> Transform::GetScale() const
{
    LogAssert(mIsRSMatrix, "Transform is not rotation-scale.");
    return Vector3<float>{ mScale[0], mScale[1], mScale[2] };
}

inline Vector4<float> Transform::GetScaleW1() const
{
    LogAssert(mIsRSMatrix, "Transform is not rotation-scale.");
    return mScale;
}

inline float Transform::GetUniformScale() const
{
    LogAssert(mIsRSMatrix, "Transform is not rotation-scale.");
    LogAssert(mIsUniformScale, "Transform is not uniform scale.");
    return mScale[0];
}

inline Matrix4x4<float> const& Transform::GetHMatrix() const
{
    return mHMatrix;
}

inline Matrix4x4<float> operator*(Matrix4x4<float> const& A,
    Transform const& B)
{
    return A * B.GetHMatrix();
}

inline Matrix4x4<float> operator*(Transform const& A,
    Matrix4x4<float> const& B)
{
    return A.GetHMatrix() * B;
}


}
